#include "fork.h"
#include "process.h"
#include "memory.h"
#include "interrupt.h"
#include "debug.h"
#include "thread.h"    
#include "string.h"
#include "file.h"

extern void intr_exit(void);

// 将父进程的 PCB、虚拟地址位图拷贝给子进程
static int32_t copy_pcb_vaddrbitmap_stack0(struct task_struct* child_thread, struct task_struct* parent_thread) {
    // 复制 PCB 所在的整个页，里面包含了进程 PCB 信息以及特权级0的栈(pcb->self_kstack)
    memcpy(child_thread, parent_thread, PG_SIZE);
    child_thread -> pid = fork_pid();
    child_thread -> elapsed_ticks = 0;
    child_thread -> status = TASK_READY;
    child_thread -> ticks = child_thread -> priority; // 重置时间片，将其填满
    child_thread -> parent_pid = parent_thread -> pid;
    child_thread -> general_tag.prev = child_thread -> general_tag.next = NULL;
    child_thread -> all_list_tag.prev = child_thread -> all_list_tag.next = NULL;
    block_desc_init(child_thread -> u_block_desc);
    // 复制父进程的虚拟内存池位图
    uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8, PG_SIZE);
    void* vaddr_btmp = get_kernel_pages(bitmap_pg_cnt);
    if(vaddr_btmp == NULL) return -1;
    // 将父进程的虚拟内存池位图复制一份给子进程, child_thread 其实也可以换成 parent_thread
    memcpy(vaddr_btmp, child_thread -> userprog_vaddr.vaddr_bitmap.bits, bitmap_pg_cnt * PG_SIZE);
    child_thread -> userprog_vaddr.vaddr_bitmap.bits = vaddr_btmp;
    ASSERT(strlen(child_thread -> name) < 11); // pcb.name 长度为 16，为避免下面 strcat 越界
    strcat(child_thread -> name, "_fork");
    return 0;
}

// 复制子进程的进程体（代码和数据）以及用户栈
static void copy_body_stack3(struct task_struct* child_thread, struct task_struct* parent_thread, void* buf_page) {
    uint8_t* vaddr_btmp = parent_thread -> userprog_vaddr.vaddr_bitmap.bits;
    uint32_t btmp_byte_len = parent_thread -> userprog_vaddr.vaddr_bitmap.btmp_bytes_len;
    uint32_t vaddr_start = parent_thread -> userprog_vaddr.vaddr_start;
    uint32_t idx_byte = 0;
    uint32_t idx_bit = 0;
    uint32_t prog_vaddr = 0;

    // 在父进程的用户空间中查找已有数据的页
    while(idx_byte < btmp_byte_len) {
        if(vaddr_btmp[idx_byte]) { // 逐个字节判断
            idx_bit = 0;
            while(idx_bit < 8) { // 逐个位判断
                if((BITMAP_MASK << idx_bit) & vaddr_btmp[idx_byte]) {
                    prog_vaddr = (idx_byte * 8 + idx_bit) * PG_SIZE + vaddr_start;

                    // 将父进程所在用户空间中的数据复制到内核缓冲区 buf_page
                    // 目的是下面切换到子进程的页表后，还能访问到父进程的数据
                    memcpy(buf_page, (void*) prog_vaddr, PG_SIZE);

                    // 将页表切换到子进程，目的是避免下面申请内存的函数将 pte 及 pde 安装到父进程的页表中
                    page_dir_activate(child_thread);
                    // 申请虚拟地址 prog_vaddr
                    get_a_page_without_opvaddrbitmap(PF_USER, prog_vaddr);

                    // 从内核缓冲区中将父进程数据复制到子进程的用户空间中
                    memcpy((void*) prog_vaddr, buf_page, PG_SIZE);

                    // 恢复父进程页表
                    page_dir_activate(parent_thread);
                }
                idx_bit++;
            }
        }
        idx_byte++;
    }
}

// 为子进程构建 thread_stack 和修改返回值
static int32_t build_child_stack(struct task_struct* child_thread) {
    // -----------------------
    // 使子进程 pid 返回 0
    // -----------------------
    // 获取子进程0级栈栈顶
    struct intr_stack* intr_0_stack = (struct intr_stack*)((uint32_t) child_thread + PG_SIZE - sizeof(struct intr_stack));
    // 修改子进程的返回值为 0
    intr_0_stack -> eax = 0;

    // 为 switch_to 构建 struct thread_stack，构建在 intr_stack 之下的空间
    uint32_t* ret_addr_in_thread_stack  = (uint32_t*)intr_0_stack - 1;

    /***   这三行不是必要的,只是为了梳理thread_stack中的关系 ***/
    uint32_t* esi_ptr_in_thread_stack = (uint32_t*)intr_0_stack - 2; 
    uint32_t* edi_ptr_in_thread_stack = (uint32_t*)intr_0_stack - 3; 
    uint32_t* ebx_ptr_in_thread_stack = (uint32_t*)intr_0_stack - 4; 
    /**********************************************************/

    /* ebp在thread_stack中的地址便是当时的esp(0级栈的栈顶),
    即esp为"(uint32_t*)intr_0_stack - 5" */
    uint32_t* ebp_ptr_in_thread_stack = (uint32_t*)intr_0_stack - 5; 

    // 更新内存中的数据
    // switch_to 的返回地址更新为 intr_exit，直接从中断返回
    *ret_addr_in_thread_stack = (uint32_t) intr_exit;

    /* 下面这两行赋值只是为了使构建的thread_stack更加清晰,其实也不需要,
     * 因为在进入intr_exit后一系列的pop会把寄存器中的数据覆盖 */
    *ebp_ptr_in_thread_stack = *ebx_ptr_in_thread_stack =\
    *edi_ptr_in_thread_stack = *esi_ptr_in_thread_stack = 0;
   /*********************************************************/

    // 把构建的 thread_stack 的栈顶作为 switch_to 恢复数据时的栈顶
    child_thread -> self_kstack = ebp_ptr_in_thread_stack;
    return 0;
}

// 更新 inode 打开数，其实就是更新文件被打开的次数
static void update_inode_open_cnts(struct task_struct* thread) {
    int32_t local_fd = 3, global_fd = 0;
    while(local_fd < MAX_FILES_OPEN_PER_PROC) {
        global_fd = thread -> fd_table[local_fd];
        ASSERT(global_fd < MAX_FILE_OPEN);
        if(global_fd != -1)
            file_table[global_fd].fd_inode->i_open_cnt++;
        local_fd++;
    }
}

// 拷贝父进程本身所占资源给子进程（对前面函数的封装罢了）
static int32_t copy_process(struct task_struct* child_thread, struct task_struct* parent_thread) {
    // 内核缓冲区，作为父进程用户空间的数据复制到子进程用户空间的中转
    void* buf_page = get_kernel_pages(1);
    if(buf_page == NULL) return -1;

    // 复制父进程的 PCB、虚拟地址位图、内核栈到子进程
    if(copy_pcb_vaddrbitmap_stack0(child_thread, parent_thread) == -1) return -1;

    // 为子进程创建页表，此页表仅包含内核空间
    child_thread -> pgdir = create_page_dir();
    if(child_thread -> pgdir == NULL) return -1;

    // 复制父进程的进程体及用户栈给子进程
    copy_body_stack3(child_thread, parent_thread, buf_page);

    // 构建子进程的 thread_stack 和修改返回值 pid
    build_child_stack(child_thread);

    // 更新文件(inode)被打开的次数
    update_inode_open_cnts(child_thread);

    mfree_page(PF_KERNEL, buf_page, 1);

    return 0;
}

// fork 子进程，内核线程不可直接调用
pid_t sys_fork(void) {
    struct task_struct* parent_thread = running_thread();
    struct task_struct* child_thread = get_kernel_pages(1);
    if(child_thread == NULL) return -1;

    ASSERT(INTR_OFF == intr_get_status() && parent_thread -> pgdir != NULL);

    if(copy_process(child_thread, parent_thread) == -1) return -1;

    // 添加到就绪队列和所有线程队列，子进程由调度器安排运行
    ASSERT(!elem_find(&thread_ready_list, &child_thread->general_tag));
    list_append(&thread_ready_list, &child_thread -> general_tag);
    ASSERT(!elem_find(&thread_all_list, &child_thread->all_list_tag));
    list_append(&thread_all_list, &child_thread -> all_list_tag);

    return child_thread -> pid; // 父进程返回子进程的 pid
}
